# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <sys/time.h>
# include <arpa/inet.h>
# include <netdb.h>
# include <unistd.h>

# define UCHAR unsigned char
# define USHORT unsigned short
# define UINT unsigned int

# define NUM 5 // 发送次数
# define ICMP_ECHO 8 
# define ICMP_ECHOREPLY 0
# define ICMP_SIZE (sizeof(struct icmp))
# define BUF_SIZE 1024

struct icmp {
	UCHAR type;
	UCHAR code;
	USHORT cksum;
	USHORT id;
	USHORT seq;
	struct timeval timestamp;
};

struct ip {
#if __BYTE_ORDER == __LITTLE_ENDIAN
	UCHAR hlen:4;
	UCHAR version:4;
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
	UCHAR version:4;
	UCHAR hlen:4;
#endif
	UCHAR tos;
	USHORT tlen;
	USHORT id;
	USHORT offset;
	UCHAR ttl;
	UCHAR protocol;
	USHORT hcksum;
	struct in_addr src;
	struct in_addr dst;
};
	
/* buf */
char buf[BUF_SIZE] = {0};
/*
 * data 为参与校验的数据
 * len 为数据的字节数
 */
USHORT checkSum(USHORT * data, int len);
/*
 * 打包函数
 * icmp为被打包的icmp包
 * seq为icmp包的序列号
 */
void pack(struct icmp * icmp, int seq);
/*
 * 解包函数
 * ip为ip封包
 * len为封包的大小
 * addr 为封包的发送地址
 */

/* 
 * 计算时间相差多少毫秒
 * begin和end分别为开始时间和结束时间
 */
float timediff(struct timeval * begin, struct timeval * end);

/*
 * 拆包
 */
int unpack(void * buf, int len, struct sockaddr_in *addr);

int main(int argc, char * argv[])
{
	int nsend;
	int nrecv;
	int i;

	struct icmp icmp = {0};

	/* 发送和接收地址 */
	struct sockaddr_in from = {0};
	struct sockaddr_in to = {0};

    int fromlen = 0;

	/* ip封包的大小 */
	int iplen = 0;
	/* socket 文件描述符*/
	int sockfd = 0;
	/* gethostbyname */
	struct hostent *host = 0;
	
	in_addr_t inaddr = 0;

	if(argc < 2)
	{
		printf("Usage : %s hostname/IP address\n", argv[0]);
		exit(1);
	}

	/* 创建socket */
	if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1){
		printf("create socket error\n");
		exit(1);
	}

	to.sin_family = AF_INET;

	/* 判断argv[1]是IP还是hostname */
	if((inaddr = inet_addr(argv[1])) == INADDR_NONE){
		/* 是hostname ? */
		if((host = gethostbyname(argv[1])) == NULL) {
			printf("Error, 地址错误\n");
			exit(1);
		}
		to.sin_addr = *(struct in_addr *)(host->h_addr);
	} else {
		// 是IP地址
		to.sin_addr.s_addr = inaddr;
	}

	printf("ping %s [%s]\n", argv[1], inet_ntoa(to.sin_addr));

    nsend = 0;
    nrecv = 0;
    fromlen = sizeof(struct sockaddr_in);
	for(i = 0; i < NUM; i++)
	{
		// 装包
        nsend ++;
        pack(&icmp, nsend);

		// 发送
        if(sendto(sockfd, &icmp, ICMP_SIZE, 0, (struct sockaddr*)&to, sizeof(to)) == -1)
           {
               /* sendto() Error */
               perror("sendto()");
               continue;
           }
		// 接收
        if((iplen=recvfrom(sockfd, &buf, BUF_SIZE, 0, (struct sockaddr*)&from, &fromlen)) < 0)
           {
               /* recvfrom() Error */
               perror("recvfrom()");
               continue;
           }
        nrecv ++;
		// 拆包
        if(-1 == unpack(&buf, iplen, &from))
           {
               printf("unpack() error\n");
           }
        sleep(1);
	}
    printf("--- %s ping statistics ---\n", argv[1]);
    printf("%d packets transmitted, %d received, %%%d packet loss\n",nsend, nrecv, (nsend - nrecv) / nsend * 100);
	return 0;
}



USHORT checkSum(USHORT * data, int len)
{
	UINT sum = 0;
	while(len > 1) {
        sum += *data;
        data ++;
		len -= 2;
	}

	// 处理剩下的一个字节
	if(1 == len)
	{
		sum += *(UCHAR*)data;
	}

	// 高16位与低16位相加
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	
	return (USHORT) ~sum;
}

float timediff(struct timeval * begin, struct timeval *end)
{
	// 计算相差的微秒数
    long diffusec = 0;

    diffusec = (end->tv_sec - begin->tv_sec) * 1000000 
        + end->tv_usec - begin->tv_usec;

    // 转换为ms
    return (float)diffusec / 1000;
}



void pack(struct icmp * icmp, int seq)
{
    icmp->type = ICMP_ECHO;
    icmp->code = 0;
    icmp->cksum = 0;
    icmp->id = getpid();
    icmp->seq = seq;
    gettimeofday(&icmp->timestamp, NULL);
    icmp->cksum = checkSum((USHORT *)icmp, ICMP_SIZE);
}


int unpack(void * buf, int len, struct sockaddr_in * addr)
{
    struct ip *ip;
    int ipheadlen;
    struct icmp *icmp;
    float time;
    struct timeval end;

    /* 获得当前时间 */
    gettimeofday(&end, NULL);

    /* 将buf转为ip指针 */
    ip = (struct ip*)buf;

    /* 计算ip头的大小 */
    ipheadlen = ip->hlen << 2;

    /* 获得icmp包 */
    icmp = (struct icmp *)(buf + ipheadlen);

    // ICMP报文的总长度
    len -= ipheadlen;

    if(len < 8) {
        printf("ICMP packets\'s length is less than 8 \n");
        return -1;
    }

    /* 判断是否为我们的程序发出的包 */
    if(icmp->type != ICMP_ECHOREPLY || icmp->id != getpid())
    {
        /* 不是我们的程序所发的包 */
        printf("ICMP packets are not send by us\n");
        return -1;
    }

    /* 计算ttl */
    time = timediff(&icmp->timestamp, &end);
    /* 输出信息 */
    printf("%ld bytes from %s: icmp_seq=%d ttl=%d time=%fms\n",
          ICMP_SIZE, inet_ntoa(addr->sin_addr), icmp->seq, ip->ttl, time);
    return 0;
}
